/*-------------------------------------------------------------------------
 *
 * chklocale.c
 *		处理与区域设置相关信息的函数
 *
 *
 * Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/port/chklocale.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif

#include "mb/pg_wchar.h"


/*
 * 此表需要识别所有支持的后端编码的CODESET拼写，以及在可能的情况下仅限前端的编码
 * （后者目前仅在initdb需要识别错误情况时使用）。在Windows上，我们依赖于代码页
 * 数（CPnnn）的条目。
 *
 * 请注意，我们是使用pg_strcasecmp()来搜索该表，因此变体的大小写不需要
 * 自己的条目。
 */
struct encoding_match
{
	enum pg_enc pg_enc_code;
	const char *system_enc_name;
};

static const struct encoding_match encoding_match_list[] = {
	{PG_EUC_JP, "EUC-JP"},
	{PG_EUC_JP, "eucJP"},
	{PG_EUC_JP, "IBM-eucJP"},
	{PG_EUC_JP, "sdeckanji"},
	{PG_EUC_JP, "CP20932"},

	{PG_EUC_CN, "EUC-CN"},
	{PG_EUC_CN, "eucCN"},
	{PG_EUC_CN, "IBM-eucCN"},
	{PG_EUC_CN, "GB2312"},
	{PG_EUC_CN, "dechanzi"},
	{PG_EUC_CN, "CP20936"},

	{PG_EUC_KR, "EUC-KR"},
	{PG_EUC_KR, "eucKR"},
	{PG_EUC_KR, "IBM-eucKR"},
	{PG_EUC_KR, "deckorean"},
	{PG_EUC_KR, "5601"},
	{PG_EUC_KR, "CP51949"},

	{PG_EUC_TW, "EUC-TW"},
	{PG_EUC_TW, "eucTW"},
	{PG_EUC_TW, "IBM-eucTW"},
	{PG_EUC_TW, "cns11643"},
	/* EUC-TW没有代码页？ */

	{PG_UTF8, "UTF-8"},
	{PG_UTF8, "utf8"},
	{PG_UTF8, "CP65001"},

	{PG_LATIN1, "ISO-8859-1"},
	{PG_LATIN1, "ISO8859-1"},
	{PG_LATIN1, "iso88591"},
	{PG_LATIN1, "CP28591"},

	{PG_LATIN2, "ISO-8859-2"},
	{PG_LATIN2, "ISO8859-2"},
	{PG_LATIN2, "iso88592"},
	{PG_LATIN2, "CP28592"},

	{PG_LATIN3, "ISO-8859-3"},
	{PG_LATIN3, "ISO8859-3"},
	{PG_LATIN3, "iso88593"},
	{PG_LATIN3, "CP28593"},

	{PG_LATIN4, "ISO-8859-4"},
	{PG_LATIN4, "ISO8859-4"},
	{PG_LATIN4, "iso88594"},
	{PG_LATIN4, "CP28594"},

	{PG_LATIN5, "ISO-8859-9"},
	{PG_LATIN5, "ISO8859-9"},
	{PG_LATIN5, "iso88599"},
	{PG_LATIN5, "CP28599"},

	{PG_LATIN6, "ISO-8859-10"},
	{PG_LATIN6, "ISO8859-10"},
	{PG_LATIN6, "iso885910"},

	{PG_LATIN7, "ISO-8859-13"},
	{PG_LATIN7, "ISO8859-13"},
	{PG_LATIN7, "iso885913"},

	{PG_LATIN8, "ISO-8859-14"},
	{PG_LATIN8, "ISO8859-14"},
	{PG_LATIN8, "iso885914"},

	{PG_LATIN9, "ISO-8859-15"},
	{PG_LATIN9, "ISO8859-15"},
	{PG_LATIN9, "iso885915"},
	{PG_LATIN9, "CP28605"},

	{PG_LATIN10, "ISO-8859-16"},
	{PG_LATIN10, "ISO8859-16"},
	{PG_LATIN10, "iso885916"},

	{PG_KOI8R, "KOI8-R"},
	{PG_KOI8R, "CP20866"},

	{PG_KOI8U, "KOI8-U"},
	{PG_KOI8U, "CP21866"},

	{PG_WIN866, "CP866"},
	{PG_WIN874, "CP874"},
	{PG_WIN1250, "CP1250"},
	{PG_WIN1251, "CP1251"},
	{PG_WIN1251, "ansi-1251"},
	{PG_WIN1252, "CP1252"},
	{PG_WIN1253, "CP1253"},
	{PG_WIN1254, "CP1254"},
	{PG_WIN1255, "CP1255"},
	{PG_WIN1256, "CP1256"},
	{PG_WIN1257, "CP1257"},
	{PG_WIN1258, "CP1258"},

	{PG_ISO_8859_5, "ISO-8859-5"},
	{PG_ISO_8859_5, "ISO8859-5"},
	{PG_ISO_8859_5, "iso88595"},
	{PG_ISO_8859_5, "CP28595"},

	{PG_ISO_8859_6, "ISO-8859-6"},
	{PG_ISO_8859_6, "ISO8859-6"},
	{PG_ISO_8859_6, "iso88596"},
	{PG_ISO_8859_6, "CP28596"},

	{PG_ISO_8859_7, "ISO-8859-7"},
	{PG_ISO_8859_7, "ISO8859-7"},
	{PG_ISO_8859_7, "iso88597"},
	{PG_ISO_8859_7, "CP28597"},

	{PG_ISO_8859_8, "ISO-8859-8"},
	{PG_ISO_8859_8, "ISO8859-8"},
	{PG_ISO_8859_8, "iso88598"},
	{PG_ISO_8859_8, "CP28598"},

	{PG_SJIS, "SJIS"},
	{PG_SJIS, "PCK"},
	{PG_SJIS, "CP932"},
	{PG_SJIS, "SHIFT_JIS"},

	{PG_BIG5, "BIG5"},
	{PG_BIG5, "BIG5HKSCS"},
	{PG_BIG5, "Big5-HKSCS"},
	{PG_BIG5, "CP950"},

	{PG_GBK, "GBK"},
	{PG_GBK, "CP936"},

	{PG_UHC, "UHC"},
	{PG_UHC, "CP949"},

	{PG_JOHAB, "JOHAB"},
	{PG_JOHAB, "CP1361"},

	{PG_GB18030, "GB18030"},
	{PG_GB18030, "CP54936"},

	{PG_SHIFT_JIS_2004, "SJIS_2004"},

	{PG_SQL_ASCII, "US-ASCII"},

	{PG_SQL_ASCII, NULL}		/* 结束标记 */
};

#ifdef WIN32
/*
 * 在Windows上，使用CP<代码页号>代替nl_langinfo()结果
 *
 * Visual Studio 2012扩展了有效的LC_CTYPE值集，因此让其
 * 本地化机制来确定代码页。请参见IsoLocaleName()中的注释。
 * 对于其他编译器，请遵循本地的可预测格式。
 *
 * Visual Studio 2015应该仍然能够做到这一点，但在_locale_t中缺少
 * lc_codepage的声明，导致此代码编译失败，因此改为使用
 * GetLocaleInfoEx。VS 2015可能是个例外，而后续的VS2015版本应该能够
 * 正确处理使用_create_locale()的代码页号。因此， 与
 * VS 2012和VS 2013相同的逻辑不同，此例程使用GetLocaleInfoEx解析
 * 短本地名称，如“de-DE”、“fr-FR”等。如果这些无法正确解析，
 * 则处理将回落到使用
 * <语言>_<国家>.<代码页>作为基础的预VS-2010手动解析。
 *
 * 返回一个由malloc()分配的字符串，供调用方释放。
 */
static char * win32_langinfo(const char *ctype)
{
	char	   *r = NULL;

#if defined(_MSC_VER) && (_MSC_VER < 1900)
	_locale_t	loct = NULL;

	loct = _create_locale(LC_CTYPE, ctype);
	if (loct != NULL)
	{
		r = malloc(16);			/* 多余 */
		if (r != NULL)
			sprintf(r, "CP%u", loct->locinfo->lc_codepage);
		_free_locale(loct);
	}
#else
	char	   *codepage;

#if defined(_MSC_VER) && (_MSC_VER >= 1900)
	uint32		cp;
	WCHAR		wctype[LOCALE_NAME_MAX_LENGTH];

	memset(wctype, 0, sizeof(wctype));
	MultiByteToWideChar(CP_ACP, 0, ctype, -1, wctype, LOCALE_NAME_MAX_LENGTH);

	if (GetLocaleInfoEx(wctype,
						LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
						(LPWSTR) &cp, sizeof(cp) / sizeof(WCHAR)) > 0)
	{
		r = malloc(16);			/* 多余 */
		if (r != NULL)
		{
			/*
			 * 如果返回值是CP_ACP，则表示不可用ANSI代码页，
			 * 因此只能使用Unicode作为本地化。
			 */
			if (cp == CP_ACP)
				strcpy(r, "utf8");
			else
				sprintf(r, "CP%u", cp);
		}
	}
	else
#endif
	{
		/*
		 * Win32上的本地格式为<语言>_<国家>.<代码页>。例如，
		 * English_United States.1252。如果我们在最后一个点之后看到数字，
		 * 假设它是代码页号。否则，我们可能正在处理Unix样式的
		 * 本地字符串；Windows的setlocale()将接受这些，尽管
		 * GetLocaleInfoEx()不会，因此我们最终会在这里。
		 * 在这种情况下，仅返回最后一个点之后的内容，并希望我们
		 * 能在我们的表中找到它。
		 */
		codepage = strrchr(ctype, '.');
		if (codepage != NULL)
		{
			size_t		ln;

			codepage++;
			ln = strlen(codepage);
			r = malloc(ln + 3);
			if (r != NULL)
			{
				if (strspn(codepage, "0123456789") == ln)
					sprintf(r, "CP%s", codepage);
				else
					strcpy(r, codepage);
			}
		}
	}
#endif

	return r;
}

#ifndef FRONTEND
/*
 * 给定Windows代码页标识符，查找相应的PostgreSQL
 * 编码。如果未找到，则发出警告并返回-1。
 */
int pg_codepage_to_encoding(UINT cp)
{
	char		sys[16];
	int			i;

	sprintf(sys, "CP%u", cp);

	/* 检查表 */
	for (i = 0; encoding_match_list[i].system_enc_name; i++)
		if (pg_strcasecmp(sys, encoding_match_list[i].system_enc_name) == 0)
			return encoding_match_list[i].pg_enc_code;

	ereport(WARNING,
			(errmsg("could not determine encoding for codeset \"%s\"", sys)));

	return -1;
}
#endif
#endif							/* WIN32 */

#if (defined(HAVE_LANGINFO_H) && defined(CODESET)) || defined(WIN32)

/*
 * 给定LC_CTYPE的设置，如果可以确定，则返回相关
 * 编码的Postgres ID。如果无法确定，则返回-1。
 *
 * 传入NULL以获取当前本地设置的编码。
 * 传入""以获取服务器环境所选的编码。
 *
 * 如果结果是PG_SQL_ASCII，调用者应将其视为与任何所需
 * 编码兼容。
 *
 * 如果在后端运行并且write_message为false，则此函数必须
 * 处理elog()和palloc()尚不可用的可能性。
 */
int pg_get_encoding_from_locale(const char *ctype, bool fc_write_message)
{
	char	   *sys;
	int			fc_i;

	/* 获取CODESET属性，如果未传入则获取LC_CTYPE */
	if (ctype)
	{
		char	   *fc_save;
		char	   *fc_name;

		/* 如果区域设置是 C 或 POSIX，我们可以允许所有编码 */
		if (pg_strcasecmp(ctype, "C") == 0 ||
			pg_strcasecmp(ctype, "POSIX") == 0)
			return PG_SQL_ASCII;

		fc_save = setlocale(LC_CTYPE, NULL);
		if (!fc_save)
			return -1;			/* setlocale() 是否损坏？ */
		/* 必须复制结果，否则可能在 setlocale 之后发生变化 */
		fc_save = strdup(fc_save);
		if (!fc_save)
			return -1;			/* 内存不足；不太可能 */

		fc_name = setlocale(LC_CTYPE, ctype);
		if (!fc_name)
		{
			free(fc_save);
			return -1;			/* 是否传入了错误的 ctype？ */
		}

#ifndef WIN32
		sys = nl_langinfo(CODESET);
		if (sys)
			sys = strdup(sys);
#else
		sys = win32_langinfo(name);
#endif

		setlocale(LC_CTYPE, fc_save);
		free(fc_save);
	}
	else
	{
		/* 更容易... */
		ctype = setlocale(LC_CTYPE, NULL);
		if (!ctype)
			return -1;			/* setlocale() 是否损坏？ */

		/* 如果区域设置是 C 或 POSIX，我们可以允许所有编码 */
		if (pg_strcasecmp(ctype, "C") == 0 ||
			pg_strcasecmp(ctype, "POSIX") == 0)
			return PG_SQL_ASCII;

#ifndef WIN32
		sys = nl_langinfo(CODESET);
		if (sys)
			sys = strdup(sys);
#else
		sys = win32_langinfo(ctype);
#endif
	}

	if (!sys)
		return -1;				/* 内存不足；不太可能 */

	/* 检查表 */
	for (fc_i = 0; encoding_match_list[fc_i].system_enc_name; fc_i++)
	{
		if (pg_strcasecmp(sys, encoding_match_list[fc_i].system_enc_name) == 0)
		{
			free(sys);
			return encoding_match_list[fc_i].pg_enc_code;
		}
	}

	/* 特定平台的特殊情况解决方案在这里 */

#ifdef __darwin__

	/*
	 * 当前的 macOS 有许多区域设置报告 CODESET 的空字符串，
	 * 但它们似乎实际上都使用 UTF-8。
	 */
	if (strlen(sys) == 0)
	{
		free(sys);
		return PG_UTF8;
	}
#endif

	/*
	 * 如果我们得到了一个 CODESET 字符串但无法识别它，我们将打印警告。
	 * 这意味着我们需要在表中添加另一个条目。
	 */
	if (fc_write_message)
	{
#ifdef FRONTEND
		fprintf(stderr, _("could not determine encoding for locale \"%s\": codeset is \"%s\""),
				ctype, sys);
		/* 保持换行符分开，以便只有一个可翻译字符串 */
		fputc('\n', stderr);
#else
		ereport(WARNING,
				(errmsg("could not determine encoding for locale \"%s\": codeset is \"%s\"",
						ctype, sys)));
#endif
	}

	free(sys);
	return -1;
}
#else							/* (HAVE_LANGINFO_H && CODESET) || WIN32 */

/*
 * 如果没有多语言平台支持，则为存根
 *
 * 注意：我们可以在这里返回 -1，但这会迫使用户在此类平台上
 * 必须指定编码来 initdb。默默地默认使用 SQL_ASCII 似乎更好。
 */
int pg_get_encoding_from_locale(const char *ctype, bool write_message)
{
	return PG_SQL_ASCII;
}

#endif							/* (HAVE_LANGINFO_H && CODESET) || WIN32 */
